"""
Feature configuration manager for CodeMCP.

Handles environment-specific feature flag configurations, deployment environments,
and configuration inheritance across different deployment stages.
"""

import os
from pathlib import Path
from typing import Dict, List, Optional, Any, Set, Union
from dataclasses import dataclass, field
from datetime import datetime
from enum import Enum

from ...core.config import Config
from ...core.error_handler import CodeMCPError, log_info, log_warning, log_debug, handle_error
from .feature_flag_manager import FeatureFlag, FeatureState


class EnvironmentType(Enum):
    """Types of deployment environments."""
    DEVELOPMENT = "development"
    TESTING = "testing"
    STAGING = "staging"
    PRODUCTION = "production"
    CANARY = "canary"
    EXPERIMENTAL = "experimental"


class ConfigurationSource(Enum):
    """Sources of feature flag configurations."""
    FILE = "file"
    ENVIRONMENT_VARIABLES = "environment_variables"
    DATABASE = "database"
    REMOTE_CONFIG = "remote_config"
    OVERRIDE = "override"


@dataclass
class EnvironmentConfig:
    """Configuration for a specific environment."""
    name: str
    environment_type: EnvironmentType
    feature_overrides: Dict[str, Any] = field(default_factory=dict)
    default_state: FeatureState = FeatureState.DISABLED
    allowed_rollout_percentage: float = 100.0
    configuration_source: ConfigurationSource = ConfigurationSource.FILE
    parent_environment: Optional[str] = None
    metadata: Dict[str, Any] = field(default_factory=dict)
    created_at: datetime = field(default_factory=datetime.utcnow)
    updated_at: datetime = field(default_factory=datetime.utcnow)
    
    def to_dict(self) -> Dict[str, Any]:
        """Convert environment config to dictionary."""
        return {
            "name": self.name,
            "environment_type": self.environment_type.value,
            "feature_overrides": self.feature_overrides,
            "default_state": self.default_state.value,
            "allowed_rollout_percentage": self.allowed_rollout_percentage,
            "configuration_source": self.configuration_source.value,
            "parent_environment": self.parent_environment,
            "metadata": self.metadata,
            "created_at": self.created_at.isoformat(),
            "updated_at": self.updated_at.isoformat()
        }
    
    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> "EnvironmentConfig":
        """Create environment config from dictionary."""
        return cls(
            name=data["name"],
            environment_type=EnvironmentType(data["environment_type"]),
            feature_overrides=data.get("feature_overrides", {}),
            default_state=FeatureState(data.get("default_state", "disabled")),
            allowed_rollout_percentage=data.get("allowed_rollout_percentage", 100.0),
            configuration_source=ConfigurationSource(data.get("configuration_source", "file")),
            parent_environment=data.get("parent_environment"),
            metadata=data.get("metadata", {}),
            created_at=datetime.fromisoformat(data.get("created_at", datetime.utcnow().isoformat())),
            updated_at=datetime.fromisoformat(data.get("updated_at", datetime.utcnow().isoformat()))
        )


@dataclass
class ConfigurationRule:
    """Rule for applying configuration overrides."""
    name: str
    condition: str  # Expression to evaluate
    overrides: Dict[str, Any]
    priority: int = 0
    active: bool = True
    environments: Set[str] = field(default_factory=set)
    
    def to_dict(self) -> Dict[str, Any]:
        """Convert configuration rule to dictionary."""
        return {
            "name": self.name,
            "condition": self.condition,
            "overrides": self.overrides,
            "priority": self.priority,
            "active": self.active,
            "environments": list(self.environments)
        }
    
    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> "ConfigurationRule":
        """Create configuration rule from dictionary."""
        return cls(
            name=data["name"],
            condition=data["condition"],
            overrides=data["overrides"],
            priority=data.get("priority", 0),
            active=data.get("active", True),
            environments=set(data.get("environments", []))
        )


class ConfigurationError(CodeMCPError):
    """Configuration specific errors."""
    
    def __init__(self, message: str, environment: str = None, **kwargs):
        super().__init__(message, "CONFIGURATION_ERROR", **kwargs)
        if environment:
            self.details["environment"] = environment


class FeatureConfigurationManager:
    """
    Feature configuration manager for CodeMCP.
    
    Handles environment-specific feature flag configurations,
    deployment environments, and configuration inheritance.
    """
    
    def __init__(self, config: Config = None):
        self.config = config or Config()
        
        # Environment configurations
        self.environments: Dict[str, EnvironmentConfig] = {}
        self.configuration_rules: List[ConfigurationRule] = []
        
        # Current environment context
        self.current_environment = "development"
        self.environment_variables_prefix = "CODEMCP_FEATURE_"
        
        # Configuration cache
        self.config_cache: Dict[str, Dict[str, Any]] = {}
        self.cache_ttl_seconds = 300  # 5 minutes
        
        # Statistics
        self.config_stats = {
            "environments_loaded": 0,
            "configuration_applications": 0,
            "cache_hits": 0,
            "cache_misses": 0,
            "rule_evaluations": 0
        }
        
        log_info("Feature configuration manager initialized")
    
    async def initialize(self):
        """Initialize the configuration manager."""
        try:
            # Load default environments
            await self._load_default_environments()
            
            # Load configuration rules
            await self._load_default_configuration_rules()
            
            # Detect current environment
            await self._detect_current_environment()
            
            log_info(f"Feature configuration manager initialized for environment: {self.current_environment}")
            
        except Exception as e:
            error_response = handle_error(e, {"operation": "configuration_manager_init"})
            raise ConfigurationError(f"Failed to initialize configuration manager: {error_response['message']}")
    
    async def _load_default_environments(self):
        """Load default environment configurations."""
        try:
            default_environments = {
                "development": EnvironmentConfig(
                    name="development",
                    environment_type=EnvironmentType.DEVELOPMENT,
                    default_state=FeatureState.CONDITIONAL,
                    allowed_rollout_percentage=100.0,
                    feature_overrides={
                        "enhanced_code_analysis": {
                            "state": "enabled",
                            "rollout_percentage": 100.0
                        },
                        "gsi_drp4dvar_integration": {
                            "state": "enabled",
                            "rollout_percentage": 100.0
                        },
                        "real_time_wiki_updates": {
                            "state": "conditional",
                            "targeting_rules": {
                                "user_groups": ["developers", "beta_users"]
                            }
                        },
                        "ml_code_suggestions": {
                            "state": "disabled"
                        }
                    },
                    metadata={
                        "description": "Development environment with most features enabled",
                        "maintainer": "development_team"
                    }
                ),
                
                "testing": EnvironmentConfig(
                    name="testing",
                    environment_type=EnvironmentType.TESTING,
                    default_state=FeatureState.ENABLED,
                    allowed_rollout_percentage=100.0,
                    parent_environment="development",
                    feature_overrides={
                        "advanced_caching": {
                            "state": "enabled",
                            "rollout_percentage": 100.0
                        },
                        "ml_code_suggestions": {
                            "state": "conditional",
                            "rollout_percentage": 25.0
                        }
                    },
                    metadata={
                        "description": "Testing environment for feature validation",
                        "maintainer": "qa_team"
                    }
                ),
                
                "staging": EnvironmentConfig(
                    name="staging",
                    environment_type=EnvironmentType.STAGING,
                    default_state=FeatureState.CONDITIONAL,
                    allowed_rollout_percentage=75.0,
                    parent_environment="testing",
                    feature_overrides={
                        "real_time_wiki_updates": {
                            "state": "rollout",
                            "rollout_percentage": 50.0
                        },
                        "ml_code_suggestions": {
                            "state": "disabled"
                        }
                    },
                    metadata={
                        "description": "Staging environment for production readiness",
                        "maintainer": "devops_team"
                    }
                ),
                
                "production": EnvironmentConfig(
                    name="production",
                    environment_type=EnvironmentType.PRODUCTION,
                    default_state=FeatureState.DISABLED,
                    allowed_rollout_percentage=25.0,
                    parent_environment="staging",
                    feature_overrides={
                        "enhanced_code_analysis": {
                            "state": "enabled",
                            "rollout_percentage": 100.0
                        },
                        "advanced_caching": {
                            "state": "enabled",
                            "rollout_percentage": 100.0
                        },
                        "gsi_drp4dvar_integration": {
                            "state": "rollout",
                            "rollout_percentage": 75.0
                        }
                    },
                    metadata={
                        "description": "Production environment with conservative feature rollout",
                        "maintainer": "sre_team"
                    }
                ),
                
                "canary": EnvironmentConfig(
                    name="canary",
                    environment_type=EnvironmentType.CANARY,
                    default_state=FeatureState.CONDITIONAL,
                    allowed_rollout_percentage=10.0,
                    parent_environment="production",
                    feature_overrides={
                        "real_time_wiki_updates": {
                            "state": "rollout",
                            "rollout_percentage": 10.0,
                            "targeting_rules": {
                                "user_groups": ["power_users"]
                            }
                        },
                        "ml_code_suggestions": {
                            "state": "rollout",
                            "rollout_percentage": 5.0,
                            "targeting_rules": {
                                "user_groups": ["beta_users", "gsi_researchers"]
                            }
                        }
                    },
                    metadata={
                        "description": "Canary environment for experimental features",
                        "maintainer": "platform_team"
                    }
                )
            }
            
            self.environments.update(default_environments)
            self.config_stats["environments_loaded"] = len(default_environments)
            
            log_info(f"Loaded {len(default_environments)} default environment configurations")
            
        except Exception as e:
            log_warning(f"Failed to load default environments: {e}")
    
    async def _load_default_configuration_rules(self):
        """Load default configuration rules."""
        try:
            default_rules = [
                ConfigurationRule(
                    name="performance_mode",
                    condition="context.get('performance_mode') == True",
                    overrides={
                        "advanced_caching": {"state": "enabled"},
                        "enhanced_code_analysis": {"state": "enabled"}
                    },
                    priority=10,
                    environments={"production", "staging"}
                ),
                
                ConfigurationRule(
                    name="debug_mode",
                    condition="context.get('debug_mode') == True",
                    overrides={
                        "enhanced_code_analysis": {"state": "enabled"},
                        "real_time_wiki_updates": {"state": "enabled"}
                    },
                    priority=5,
                    environments={"development", "testing"}
                ),
                
                ConfigurationRule(
                    name="gsi_researcher_mode",
                    condition="'gsi_researchers' in context.get('user_groups', [])",
                    overrides={
                        "gsi_drp4dvar_integration": {"state": "enabled"},
                        "enhanced_code_analysis": {"state": "enabled"}
                    },
                    priority=15,
                    environments={"development", "testing", "staging", "production"}
                ),
                
                ConfigurationRule(
                    name="maintenance_mode",
                    condition="context.get('maintenance_mode') == True",
                    overrides={
                        "real_time_wiki_updates": {"state": "disabled"},
                        "ml_code_suggestions": {"state": "disabled"}
                    },
                    priority=20,
                    environments={"production", "staging"}
                )
            ]
            
            self.configuration_rules.extend(default_rules)
            
            log_info(f"Loaded {len(default_rules)} default configuration rules")
            
        except Exception as e:
            log_warning(f"Failed to load default configuration rules: {e}")
    
    async def _detect_current_environment(self):
        """Detect the current environment from environment variables or config."""
        try:
            # Check environment variables
            env_name = os.getenv("CODEMCP_ENVIRONMENT")
            if not env_name:
                env_name = os.getenv("ENVIRONMENT")
            if not env_name:
                env_name = os.getenv("NODE_ENV")
            
            # Check config
            if not env_name and hasattr(self.config, 'environment'):
                env_name = self.config.environment
            
            # Default to development
            if not env_name:
                env_name = "development"
            
            if env_name in self.environments:
                self.current_environment = env_name
            else:
                log_warning(f"Unknown environment '{env_name}', defaulting to development")
                self.current_environment = "development"
            
            log_info(f"Detected current environment: {self.current_environment}")
            
        except Exception as e:
            log_warning(f"Failed to detect environment: {e}")
            self.current_environment = "development"
    
    async def register_environment(self, environment_config: EnvironmentConfig) -> bool:
        """Register an environment configuration."""
        try:
            self.environments[environment_config.name] = environment_config
            
            # Clear related cache entries
            await self._clear_environment_cache(environment_config.name)
            
            log_info(f"Registered environment configuration: {environment_config.name}")
            return True
            
        except Exception as e:
            error_response = handle_error(e, {
                "operation": "register_environment",
                "environment": environment_config.name
            })
            raise ConfigurationError(f"Failed to register environment: {error_response['message']}")
    
    async def add_configuration_rule(self, rule: ConfigurationRule) -> bool:
        """Add a configuration rule."""
        try:
            # Remove existing rule with same name
            self.configuration_rules = [r for r in self.configuration_rules if r.name != rule.name]
            
            # Add new rule
            self.configuration_rules.append(rule)
            
            # Sort by priority (higher priority first)
            self.configuration_rules.sort(key=lambda r: r.priority, reverse=True)
            
            # Clear cache since rules changed
            self.config_cache.clear()
            
            log_info(f"Added configuration rule: {rule.name}")
            return True
            
        except Exception as e:
            error_response = handle_error(e, {
                "operation": "add_configuration_rule",
                "rule_name": rule.name
            })
            raise ConfigurationError(f"Failed to add configuration rule: {error_response['message']}")
    
    async def get_feature_configuration(self, 
                                      flag_name: str,
                                      environment: str = None,
                                      context: Dict[str, Any] = None) -> Dict[str, Any]:
        """Get feature configuration for a specific flag and environment."""
        try:
            environment = environment or self.current_environment
            context = context or {}
            
            # Check cache first
            cache_key = f"{flag_name}:{environment}:{hash(str(sorted(context.items())))}"
            cached_config = await self._get_cached_config(cache_key)
            if cached_config is not None:
                self.config_stats["cache_hits"] += 1
                return cached_config
            
            self.config_stats["cache_misses"] += 1
            
            # Build configuration
            config = await self._build_feature_configuration(flag_name, environment, context)
            
            # Cache the result
            await self._cache_configuration(cache_key, config)
            
            self.config_stats["configuration_applications"] += 1
            
            return config
            
        except Exception as e:
            error_response = handle_error(e, {
                "operation": "get_feature_configuration",
                "flag_name": flag_name,
                "environment": environment
            })
            raise ConfigurationError(f"Failed to get feature configuration: {error_response['message']}")
    
    async def _build_feature_configuration(self, 
                                         flag_name: str,
                                         environment: str,
                                         context: Dict[str, Any]) -> Dict[str, Any]:
        """Build feature configuration by applying inheritance and rules."""
        try:
            # Start with base configuration
            config = {
                "state": "disabled",
                "rollout_percentage": 0.0,
                "targeting_rules": {},
                "metadata": {}
            }
            
            # Apply environment chain (from parent to child)
            env_chain = await self._get_environment_chain(environment)
            
            for env_name in env_chain:
                env_config = self.environments.get(env_name)
                if not env_config:
                    continue
                
                # Apply default state
                config["state"] = env_config.default_state.value
                
                # Apply environment-specific overrides
                if flag_name in env_config.feature_overrides:
                    flag_overrides = env_config.feature_overrides[flag_name]
                    config.update(flag_overrides)
                
                # Respect rollout limits
                if config.get("rollout_percentage", 0) > env_config.allowed_rollout_percentage:
                    config["rollout_percentage"] = env_config.allowed_rollout_percentage
            
            # Apply environment variables
            await self._apply_environment_variables(flag_name, config)
            
            # Apply configuration rules
            await self._apply_configuration_rules(flag_name, environment, context, config)
            
            # Add metadata
            config["metadata"]["environment"] = environment
            config["metadata"]["applied_at"] = datetime.utcnow().isoformat()
            
            return config
            
        except Exception as e:
            log_warning(f"Failed to build feature configuration: {e}")
            return {"state": "disabled", "rollout_percentage": 0.0}
    
    async def _get_environment_chain(self, environment: str) -> List[str]:
        """Get the inheritance chain for an environment."""
        chain = []
        current_env = environment
        visited = set()
        
        while current_env and current_env not in visited:
            visited.add(current_env)
            chain.append(current_env)
            
            env_config = self.environments.get(current_env)
            if env_config and env_config.parent_environment:
                current_env = env_config.parent_environment
            else:
                break
        
        # Return in reverse order (parent first)
        return list(reversed(chain))
    
    async def _apply_environment_variables(self, flag_name: str, config: Dict[str, Any]):
        """Apply environment variable overrides."""
        try:
            # Check for flag-specific environment variables
            flag_var_prefix = f"{self.environment_variables_prefix}{flag_name.upper()}_"
            
            # State override
            state_var = f"{flag_var_prefix}STATE"
            if state_var in os.environ:
                config["state"] = os.environ[state_var].lower()
            
            # Rollout percentage override
            rollout_var = f"{flag_var_prefix}ROLLOUT_PERCENTAGE"
            if rollout_var in os.environ:
                try:
                    config["rollout_percentage"] = float(os.environ[rollout_var])
                except ValueError:
                    log_warning(f"Invalid rollout percentage in {rollout_var}: {os.environ[rollout_var]}")
            
            # Enabled/disabled shortcuts
            enabled_var = f"{flag_var_prefix}ENABLED"
            if enabled_var in os.environ:
                value = os.environ[enabled_var].lower()
                if value in ["true", "1", "yes", "on"]:
                    config["state"] = "enabled"
                elif value in ["false", "0", "no", "off"]:
                    config["state"] = "disabled"
            
        except Exception as e:
            log_debug(f"Failed to apply environment variables for {flag_name}: {e}")
    
    async def _apply_configuration_rules(self, 
                                       flag_name: str,
                                       environment: str,
                                       context: Dict[str, Any],
                                       config: Dict[str, Any]):
        """Apply configuration rules to the feature configuration."""
        try:
            for rule in self.configuration_rules:
                if not rule.active:
                    continue
                
                # Check if rule applies to this environment
                if rule.environments and environment not in rule.environments:
                    continue
                
                # Evaluate rule condition
                self.config_stats["rule_evaluations"] += 1
                
                try:
                    # Create evaluation context
                    eval_context = {
                        "context": context,
                        "flag_name": flag_name,
                        "environment": environment,
                        "config": config
                    }
                    
                    # Evaluate condition
                    if eval(rule.condition, {"__builtins__": {}}, eval_context):
                        # Apply rule overrides
                        if flag_name in rule.overrides:
                            flag_overrides = rule.overrides[flag_name]
                            config.update(flag_overrides)
                            
                            log_debug(f"Applied rule {rule.name} to {flag_name}")
                        
                        # Apply global overrides
                        for override_key, override_value in rule.overrides.items():
                            if override_key not in [flag_name] and override_key in config:
                                config[override_key] = override_value
                
                except Exception as eval_error:
                    log_warning(f"Failed to evaluate rule {rule.name}: {eval_error}")
                    continue
            
        except Exception as e:
            log_warning(f"Failed to apply configuration rules: {e}")
    
    async def _get_cached_config(self, cache_key: str) -> Optional[Dict[str, Any]]:
        """Get cached configuration."""
        try:
            if cache_key in self.config_cache:
                cache_entry = self.config_cache[cache_key]
                
                # Check if cache is still valid
                import time
                if time.time() < cache_entry["expires_at"]:
                    return cache_entry["config"]
                else:
                    # Remove expired entry
                    del self.config_cache[cache_key]
            
            return None
            
        except Exception as e:
            log_debug(f"Failed to get cached config: {e}")
            return None
    
    async def _cache_configuration(self, cache_key: str, config: Dict[str, Any]):
        """Cache configuration."""
        try:
            import time
            expires_at = time.time() + self.cache_ttl_seconds
            
            self.config_cache[cache_key] = {
                "config": config.copy(),
                "expires_at": expires_at
            }
            
        except Exception as e:
            log_debug(f"Failed to cache configuration: {e}")
    
    async def _clear_environment_cache(self, environment: str):
        """Clear cache entries for a specific environment."""
        try:
            keys_to_remove = [
                key for key in self.config_cache.keys()
                if f":{environment}:" in key
            ]
            
            for key in keys_to_remove:
                del self.config_cache[key]
            
        except Exception as e:
            log_debug(f"Failed to clear environment cache: {e}")
    
    async def get_environment_configuration(self, environment: str = None) -> Optional[EnvironmentConfig]:
        """Get configuration for a specific environment."""
        environment = environment or self.current_environment
        return self.environments.get(environment)
    
    async def list_environments(self) -> List[EnvironmentConfig]:
        """List all environment configurations."""
        return list(self.environments.values())
    
    async def set_current_environment(self, environment: str) -> bool:
        """Set the current environment."""
        try:
            if environment not in self.environments:
                raise ConfigurationError(f"Unknown environment: {environment}")
            
            old_environment = self.current_environment
            self.current_environment = environment
            
            # Clear cache since environment changed
            self.config_cache.clear()
            
            log_info(f"Changed environment from {old_environment} to {environment}")
            return True
            
        except Exception as e:
            error_response = handle_error(e, {
                "operation": "set_current_environment",
                "environment": environment
            })
            raise ConfigurationError(f"Failed to set current environment: {error_response['message']}")
    
    async def validate_environment_config(self, environment: str = None) -> Dict[str, Any]:
        """Validate environment configuration."""
        try:
            environment = environment or self.current_environment
            env_config = self.environments.get(environment)
            
            if not env_config:
                return {
                    "valid": False,
                    "errors": [f"Environment '{environment}' not found"],
                    "warnings": []
                }
            
            errors = []
            warnings = []
            
            # Check parent environment exists
            if env_config.parent_environment:
                if env_config.parent_environment not in self.environments:
                    errors.append(f"Parent environment '{env_config.parent_environment}' not found")
            
            # Check for circular inheritance
            chain = await self._get_environment_chain(environment)
            if len(chain) != len(set(chain)):
                errors.append("Circular inheritance detected in environment chain")
            
            # Validate feature overrides
            for flag_name, overrides in env_config.feature_overrides.items():
                if isinstance(overrides, dict):
                    if "rollout_percentage" in overrides:
                        rollout = overrides["rollout_percentage"]
                        if not 0 <= rollout <= 100:
                            warnings.append(
                                f"Invalid rollout percentage for {flag_name}: {rollout}"
                            )
                        
                        if rollout > env_config.allowed_rollout_percentage:
                            warnings.append(
                                f"Rollout percentage for {flag_name} exceeds environment limit"
                            )
            
            return {
                "valid": len(errors) == 0,
                "errors": errors,
                "warnings": warnings,
                "environment": environment,
                "inheritance_chain": chain
            }
            
        except Exception as e:
            error_response = handle_error(e, {
                "operation": "validate_environment_config",
                "environment": environment
            })
            raise ConfigurationError(f"Failed to validate environment config: {error_response['message']}")
    
    async def get_configuration_stats(self) -> Dict[str, Any]:
        """Get configuration manager statistics."""
        stats = self.config_stats.copy()
        stats["current_environment"] = self.current_environment
        stats["environments_registered"] = len(self.environments)
        stats["configuration_rules"] = len(self.configuration_rules)
        stats["cache_size"] = len(self.config_cache)
        stats["cache_hit_rate"] = (
            self.config_stats["cache_hits"] /
            max(1, self.config_stats["cache_hits"] + self.config_stats["cache_misses"])
        )
        
        return stats
    
    async def export_environment_config(self, environment: str = None, format: str = "json") -> str:
        """Export environment configuration."""
        try:
            environment = environment or self.current_environment
            env_config = self.environments.get(environment)
            
            if not env_config:
                raise ConfigurationError(f"Environment '{environment}' not found")
            
            if format.lower() == "json":
                import json
                return json.dumps(env_config.to_dict(), indent=2)
            elif format.lower() == "yaml":
                import yaml
                return yaml.dump(env_config.to_dict(), default_flow_style=False)
            else:
                raise ConfigurationError(f"Unsupported export format: {format}")
            
        except Exception as e:
            error_response = handle_error(e, {
                "operation": "export_environment_config",
                "environment": environment,
                "format": format
            })
            raise ConfigurationError(f"Failed to export environment config: {error_response['message']}")